/*
 * File     : PointerList.java
 * Created  : 27 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal;

import static com.googlecode.dni.internal.MemoryAccess.MEMORY_ACCESSOR;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

import com.googlecode.dni.type.NativeObject;
import com.googlecode.dni.type.NativeObjectList;
import com.googlecode.dni.type.Pointer;
import com.googlecode.dni.type.nativetype.NativeTypes;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;


/**
 * Implementation of {@link NativeObjectList} that holds pointers.
 *
 * @author Matthew Wilson
 */
final class PointerList implements NativeObjectList< Pointer >
{

    private static final boolean ADDRESS_IS_64;

    static
    {
        if ( NativeTypes.POINTER_SIZE == 4 )
        {
            ADDRESS_IS_64 = false;
        }
        else if ( NativeTypes.POINTER_SIZE == 8 )
        {
            ADDRESS_IS_64 = true;
        }
        else
        {
            throw new AssertionError( "Unsupported pointer size" );
        }
    }


    private final long pointer;

    private final int size;


    /**
     * @param pointer
     *            the base pointer
     * @param size
     *            the number of elements
     */
    PointerList( final long pointer, final int size )
    {
        this.pointer = pointer;
        this.size = size;
    }

    /**
     * Creates an instance from a region.
     *
     * @param parent
     *            the parent
     * @param fromIndex
     *            the index of the first element (inclusive)
     * @param toIndex
     *            the index of the last element (exclusive)
     */
    PointerList( final PointerList parent, final int fromIndex, final int toIndex )
    {
        if ( fromIndex < 0 || toIndex > parent.size || fromIndex > toIndex )
        {
            throw new IndexOutOfBoundsException( fromIndex + " " + toIndex + ": size = " + parent.size );
        }

        this.pointer = parent.pointer + (long) NativeTypes.POINTER_SIZE * fromIndex;
        this.size = toIndex - fromIndex;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int size()
    {
        return this.size;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEmpty()
    {
        return this.size == 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean contains( final Object o )
    {
        return indexOf( o ) >= 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterator< Pointer > iterator()
    {
        return new PointerListIterator( 0 );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object[] toArray()
    {
        return toArray( new Object[ this.size ] );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings( "unchecked" )
    public < T > T[] toArray( final T[] a )
    {
        T[] array = a;
        if ( array.length < this.size )
        {
            T[] argh = (T[]) Array.newInstance( a.getClass().getComponentType(), this.size );
            array = argh;
        }

        if ( ADDRESS_IS_64 )
        {
            int i = 0;
            for ( long child = this.pointer; child < this.pointer + this.size * 8L; child += 8 )
            {
                array[ i++ ] = (T) Pointer.fromAddress( MEMORY_ACCESSOR.getLong( child ) );
            }
        }
        else
        {
            int i = 0;
            for ( long child = this.pointer; child < this.pointer + this.size * 4L; child += 4 )
            {
                array[ i++ ] = (T) Pointer.fromAddress( MEMORY_ACCESSOR.getInt( child ) & 0xffffffffL );
            }
        }

        if ( array.length > this.size )
        {
            array[ this.size ] = null;
        }

        return array;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean add( final Pointer e )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean remove( final Object o )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsAll( final Collection< ? > c )
    {
        for ( Object object : c )
        {
            if ( !contains( object ) )
            {
                return false;
            }
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addAll( final Collection< ? extends Pointer > c )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addAll( final int index, final Collection< ? extends Pointer > c )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean removeAll( final Collection< ? > c )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean retainAll( final Collection< ? > c )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear()
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Pointer get( final int index )
    {
        if ( index < 0 || index >= this.size )
        {
            throw new IndexOutOfBoundsException();
        }

        if ( ADDRESS_IS_64 )
        {
            return Pointer.fromAddress( MEMORY_ACCESSOR.getLong( this.pointer + index * 8L ) );
        }
        return Pointer.fromAddress( MEMORY_ACCESSOR.getInt( this.pointer + index * 4L ) & 0xffffffffL );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Pointer set( final int index, final Pointer element )
    {
        if ( index < 0 || index >= this.size )
        {
            throw new IndexOutOfBoundsException();
        }

        long newValue = element.address();

        if ( ADDRESS_IS_64 )
        {
            long child = this.pointer + index * 8L;
            Pointer old = Pointer.fromAddress( MEMORY_ACCESSOR.getLong( child ) );
            MEMORY_ACCESSOR.putLong( child, newValue );
            return old;
        }

        long child = this.pointer + index * 4L;
        Pointer old = Pointer.fromAddress( MEMORY_ACCESSOR.getInt( child ) & 0xffffffffL );
        MEMORY_ACCESSOR.putInt( child, (int) newValue );
        return old;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void add( final int index, final Pointer element )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Pointer remove( final int index )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int indexOf( final Object o )
    {
        if ( o == null || this.size == 0 || o.getClass() != Pointer.class )
        {
            return -1;
        }

        Pointer element = (Pointer) o;

        if ( ADDRESS_IS_64 )
        {
            long lookingFor = element.address();
            for ( int i = 0; i < this.size; i++ )
            {
                if ( MEMORY_ACCESSOR.getLong( this.pointer + i * 8L ) == lookingFor )
                {
                    return i;
                }
            }
        }
        else
        {
            int lookingFor = (int) element.address();
            for ( int i = 0; i < this.size; i++ )
            {
                if ( MEMORY_ACCESSOR.getInt( this.pointer + i * 4L ) == lookingFor )
                {
                    return i;
                }
            }
        }

        return -1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int lastIndexOf( final Object o )
    {
        if ( o == null || this.size == 0 || o.getClass() != Pointer.class )
        {
            return -1;
        }

        Pointer element = (Pointer) o;

        if ( ADDRESS_IS_64 )
        {
            long lookingFor = element.address();
            for ( int i = this.size - 1; i >= 0; i-- )
            {
                if ( MEMORY_ACCESSOR.getLong( this.pointer + i * 8L ) == lookingFor )
                {
                    return i;
                }
            }
        }
        else
        {
            int lookingFor = (int) element.address();
            for ( int i = this.size - 1; i >= 0; i-- )
            {
                if ( MEMORY_ACCESSOR.getInt( this.pointer + i * 4L ) == lookingFor )
                {
                    return i;
                }
            }
        }

        return -1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ListIterator< Pointer > listIterator()
    {
        return new PointerListIterator( 0 );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ListIterator< Pointer > listIterator( final int index )
    {
        if ( index < 0 || index > this.size )
        {
            throw new IndexOutOfBoundsException();
        }

        return new PointerListIterator( index );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Pointer pointer()
    {
        return Pointer.fromAddress( this.pointer );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isSameObject( final NativeObject other )
    {
        return other instanceof PointerList && other.pointer().equals( pointer() );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public NativeObjectList< Pointer > subList( final int fromIndex, final int toIndex )
    {
        return new PointerList( this, fromIndex, toIndex );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void free()
    {
        MemoryAccess.ALLOCATOR.free( pointer() );
    }

    /**
     * {@inheritDoc}
     */
    @SuppressFBWarnings( value = "NSE_NON_SYMMETRIC_EQUALS",
                         justification = "java.util.List defines its own rules" )
    @Override
    public boolean equals( final Object obj )
    {
        if ( obj == null || !( obj instanceof List< ? > ) )
        {
            return false;
        }
        if ( obj == this )
        {
            return true;
        }

        List< ? > other = (List< ? >) obj;
        if ( other.size() != this.size )
        {
            return false;
        }

        int i = 0;
        for ( Object object : other )
        {
            if ( object == null || object.getClass() != Pointer.class )
            {
                return false;
            }

            long actual = ( (Pointer) object ).address();
            if ( ADDRESS_IS_64 )
            {
                if ( actual != MEMORY_ACCESSOR.getLong( this.pointer + i * 8L ) )
                {
                    return false;
                }
            }
            else
            {
                // note: cast long to int first because otherwise we fall foul
                // of sign extension
                if ( (int) actual != MEMORY_ACCESSOR.getInt( this.pointer + i * 8L ) )
                {
                    return false;
                }
            }

            i++;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode()
    {
        int hash = 0;
        if ( ADDRESS_IS_64 )
        {
            // behave as though the values are longs
            // (same hash algorithm as Pointer)
            for ( int i = 0; i < this.size; i++ )
            {
                int value1 = MEMORY_ACCESSOR.getInt( this.pointer + i * 8L );
                int value2 = MEMORY_ACCESSOR.getInt( this.pointer + i * 8L + 4 );
                hash = hash * 31 + ( value1 ^ value2 );
            }
        }
        else
        {
            // again, same algorithm, but note that the top 32-bits will always
            // be zero
            for ( int i = 0; i < this.size; i++ )
            {
                int value = MEMORY_ACCESSOR.getInt( this.pointer + i * 4L );
                hash = hash * 31 + value;
            }
        }
        return hash;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        if ( this.size == 0 )
        {
            return "[]";
        }

        StringBuilder str = new StringBuilder( 20 * this.size + 2 );
        str.append( '[' ).append( get( 0 ) );

        for ( int i = 1; i < this.size; i++ )
        {
            str.append( ',' ).append( get( i ) );
        }

        return str.append( ']' ).toString();
    }

    private final class PointerListIterator implements ListIterator< Pointer >
    {

        private int index;

        /** Index of the last-returned element. */
        private int lastIndex = -1;

        /**
         * @param index
         *            the initial index
         */
        PointerListIterator( final int index )
        {
            this.index = index;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean hasNext()
        {
            return this.index < PointerList.this.size;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Pointer next()
        {
            if ( !hasNext() )
            {
                throw new NoSuchElementException();
            }

            this.lastIndex = this.index;
            this.index++;
            return get( this.lastIndex );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean hasPrevious()
        {
            return this.index > 0;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Pointer previous()
        {
            if ( !hasPrevious() )
            {
                throw new NoSuchElementException();
            }

            this.index--;
            this.lastIndex = this.index;
            return get( this.lastIndex );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int nextIndex()
        {
            return this.index;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int previousIndex()
        {
            return this.index - 1;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void remove()
        {
            throw new UnsupportedOperationException();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void set( final Pointer e )
        {
            if ( this.lastIndex < 0 )
            {
                throw new IllegalStateException();
            }

            PointerList.this.set( this.lastIndex, e );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void add( final Pointer e )
        {
            throw new UnsupportedOperationException();
        }

    }

}
